# -*- coding: utf-8 -*-
#'django-serializable'
#Copyright (C) 2010 Sébastien Piquemal @ futurice
#contact : sebastien.piquemal@futurice.com
#futurice's website : www.futurice.com

#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.

#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.

#You should have received a copy of the GNU General Public License
#along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
For each one of these views, the keyword argument `model` is mandatory.

For :func:`delete_serializable` and :func:`get_or_post_serializable`, it is also mandatory to provide a natural key by passing each pair `(nk_field_name, value)` as a keyword argument.

This can be done by mapping directly in the url. For example : ::
    
    url('^someurl/(?<nk_field_name_1>\w+)/(?<nk_field_name_2>\w*/$', get_or_post_serializable, kwargs={'model': MyModel})

In this case, any request issued for example to the url `'/someurl/jj/cale/'`, will result in the keywords arguments `(nk_field_name_1='jj', nk_field_name_2='cale')` passed to the view, and therefore the object accessed is identified by the natural key `('jj', 'cale')`.

This can also be done by providing explicitely these natural key values as arguments : ::
    
    url('^someurl/(?<nk_field_name_2>\d*/$', get_or_post_serializable, kwargs={'nk_field_name_1': 'jj'})

.. note:: In the following example `('nk_field_name_1', 'nk_field_name_2')` is the natural key blueprint for `model`. See :attr:`serializable.models.Serializable.nk_field_names` for more info.
"""
import pickle
import json

from django.http import Http404, HttpResponse, HttpResponseNotAllowed
from django.core.exceptions import PermissionDenied
from django.utils.datastructures import MultiValueDictKeyError

from .models import *

def get_or_post_serializable(request, model=None, login_required=False, format="pickle", **kwargs):
    """
    This view allows to get, create or update a serializable object.

    If the request issued is a POST, either the object with natural key passed as keyword arguments exist, then it is updated ; or it doesn't exist, then it is created.

        - If creating or updating : the data POSTed in the `serializable_data` parameter of the request (i.e. `request.POST['serializable_data']`) will be decoded (according to `<format>`), then passed to the :meth:`serializable.models.Serializable.deserialize` method as a python dictionnary to populate object fields.
        - If creating : the natural key's `(nk_field_name, value)` pairs will be passed along with the decoded data in the python dictionnary.
        - In all cases : the object (created, updated or unchanged) is sent as an answer, encoded in `<format>`.

    :param model: The model to manage. Must be a subclass of :class:`serializable.models.Serializable`
    :param format: The format used to decode the request parameters, and to encode the answer.
    
    .. note:: Pretty much all the serialization/deserialization work is handled by the model. So the behaviour depends on the way you parametered them.
    """
    if login_required and not request.user.is_authenticated():
        raise PermissionDenied
    if model == None:
        raise AttributeError("the keyword argument 'model' must be defined")

    try :
        natural_key = [kwargs.pop(name) for name in model.nk_field_names]
    except KeyError:
        raise Http404

    if request.method == "POST":
        try :
            serializable_data = str(request.POST["serializable_data"])
        except MultiValueDictKeyError :
            raise MultiValueDictKeyError("When POSTing serializable's data, the request parameter '%s' needs to be provided" % "serializable_data")
        if format == "pickle":
            data = pickle.loads(serializable_data)
        if format == "json":
            data = json.loads(serializable_data)
        #instance of a Serializable object
        ser = None
        try:
            ser = model.objects.get_by_natural_key(*natural_key)
        except model.DoesNotExist:
            #deserializing also natural key from the url to the new object
            [data.setdefault(name, value) for name, value in zip(model.nk_field_names, natural_key)]
            ser = model.deserialize(data)
        else :
            ser.update(data)
        ser.save()
    elif request.method == "GET":
        try:
            ser = model.objects.get_by_natural_key(*natural_key)
        except model.DoesNotExist :
            raise Http404

    if format == "pickle":
        return HttpResponse(pickle.dumps(ser.serialize()), mimetype="application/x-pickle")
    if format == "json":
        return HttpResponse(json.dumps(ser.serialize()), mimetype="application/json")

def delete_serializable(request, model=None, login_required=False, **kwargs):
    """
    This view allows to delete a serializable object.
    
    The object is fetched with its natural key, and deleted from the database.

    :param model: The model to manage. Must be a subclass of :class:`serializable.models.Serializable`
    """
    if login_required and not request.user.is_authenticated():
        raise PermissionDenied
    if model == None:
        raise AttributeError("the keyword argument 'model' must be defined")
    
    try :
        natural_key = [kwargs.pop(name) for name in model.nk_field_names]
    except KeyError:
        raise Http404
    
    if request.method == "POST":
        try :
            ser = model.objects.get_by_natural_key(*natural_key)
        except model.DoesNotExist :
            raise Http404
        else :
            ser.delete()
            return HttpResponse()

    elif request.method == "GET" :
        return HttpResponseNotAllowed(['POST'])

def get_serializable_list (request, model=None, login_required=False, format="pickle", filters=[], **kwargs):
    """
    This view allows to get a list of serializable objects.

    You can filter the results that this method send. For example, you can declare the following in your urlconf : ::

        url('^someurl/(?<author__family__name>\w+)/$', kwargs={filters: ['author__family__name'], 'model': MyModel})

    If issuing a request to `'/someurl/marshall/'`, the results in the list will be only the elements of `MyModel` whose author's family's name is `'marshall'`.

    :param model: The model to manage. Must be a subclass of :class:`serializable.models.Serializable`
    :param format: The format used to encode the answer.
    :param filters: A list of attribute filters. Supported syntax is the same as `field lookup <http://docs.djangoproject.com/en/dev/ref/models/querysets/#id7>`_ syntax. The filters declared in this list should correspond to keyword arguments passed when the view is called.

    .. note:: Pretty much all the serialization work is handled by the model. So the behaviour depends on the way you parametered them.
    """
    if login_required and not request.user.is_authenticated():
        raise PermissionDenied
    if model == None:
        raise AttributeError("the keyword argument 'model' must be defined")
    
    filter_dict = dict()
    for filter_name in filters :
        try :
            filter_dict[filter_name] = kwargs[filter_name]
        except KeyError:
            raise Http404

    if request.method == "GET" :
        if filters :
            serialized_objs = [ser.serialize() for ser in model.objects.filter(**filter_dict)]
        else :
            serialized_objs = [ser.serialize() for ser in model.objects.all()]
        if format == "pickle":
            return HttpResponse(pickle.dumps(serialized_objs), mimetype="application/x-pickle")
        if format == "json":
            return HttpResponse(json.dumps(serialized_objs), mimetype="application/json")

    elif request.method == "POST":
        return HttpResponseNotAllowed(['GET'])
